//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using Flare.Display;
using UnityEngine;

namespace Flare.Media
{   
    internal class SoundStream
    {
        private const string GAME_OBJECT_NAME = "_FlareSoundStream";
        private static GameObject ms_soundGameObject;

        internal MovieClip clip;
        internal int playbackRate;
        internal int playbackSize;
        internal int playbackType;
        internal int format;
        internal int frequency;
        internal int soundSize;
        internal int channels;
        internal int avgSampleCount;
        internal int sampleCount;
        
        internal bool isPlaying
        {
            get { return (audioSource != null) && (audioSource.isPlaying); }
        }
        
        private AudioClip audioClip;
        private AudioSource audioSource;

        private int loadedSamples;
        
        internal SoundStream(MovieClip clip, int playbackRate, int playbackSize, int playbackType, 
            int format, int frequency, int soundSize, int channels, int avgSampleCount)
        {
            // Initialize values and properties
            this.clip = clip;
            this.playbackRate = playbackRate;
            this.playbackSize = playbackSize;
            this.playbackType = playbackType;
            this.format = format;
            this.frequency = frequency;
            this.soundSize = soundSize;
            this.channels = channels;
            this.avgSampleCount = avgSampleCount;
            this.sampleCount = 0;
            this.loadedSamples = 0;
            
            // Check if the Flare sound stream game object exist, and if not create it
            if (SoundStream.ms_soundGameObject == null)
            {
                SoundStream.ms_soundGameObject = GameObject.Find(SoundStream.GAME_OBJECT_NAME);
                if (SoundStream.ms_soundGameObject == null)
                {
                    SoundStream.ms_soundGameObject = new GameObject(SoundStream.GAME_OBJECT_NAME);
                    SoundStream.ms_soundGameObject.hideFlags = HideFlags.HideAndDontSave;
                }
            }
        }
        
        internal void Play(float[] data)
        {
            // Ensure we have an audio source to work with
            if (this.audioSource == null)
            {
                AudioSource[] sources = SoundStream.ms_soundGameObject.GetComponents<AudioSource>();
                
                // Find a free audio source and use it to play the sound
                for (int i = 0; i < sources.Length; ++i)
                {
                    if (!sources[i].isPlaying)
                    {
                        this.audioSource = sources[i];
                        break;
                    }
                }
                
                // No free audio source so create a new one
                if (this.audioSource == null)
                {
                    this.audioSource = SoundStream.ms_soundGameObject.AddComponent<AudioSource>();
                }
            }

            // Create the clip if it doesn't exist
            if (this.audioClip == null)
            {
                this.audioClip = AudioClip.Create("SoundStream", sampleCount, channels, frequency,
#if UNITY_5
                    false);
#else
                    false, false);
#endif
                this.audioSource.clip = audioClip;
            }
            
            // Read audio data into the clip
            audioClip.SetData(data, loadedSamples);
            loadedSamples += (data.Length / channels);
            
            // If more audio data is needed (doesn't have 2 unplayed blocks loaded) skip the frame
            if (this.audioSource.timeSamples + 2 * avgSampleCount >= loadedSamples)
            {
                clip.NextFrame();
                clip.Play();
            }
            
            // Play the sound if it is not playing and has 2 blocks of data loaded
            if (!audioSource.isPlaying &&
                (audioSource.timeSamples + 2 * avgSampleCount < loadedSamples))
            {
                audioSource.Play();
            }
        }
        
        internal void Stop()
        {
            // Stop the sound if it is playing and reset the loaded samples
            if ((this.audioSource != null) && this.audioSource.isPlaying)
            {
                this.audioSource.Stop();
                this.loadedSamples = 0;
            }

            this.audioSource = null;
        }
    }   
}
